iT邦幫忙

2024 iThome 鐵人賽

DAY 24
2
Kubernetes

都什麼年代了,還在學 Kubernetes系列 第 24

學 Kubernetes 的第二十四天 - Deployment strategy - 實作 (2)

  • 分享至 

  • xImage
  •  

昨天的章節,我們實作了 RecreateRolling update 兩個部署策略。今天來繼續實作剩下的策略。

實作: Canary

在 Kubernetes 中,可以使用兩個具有相同 Pod 標籤的 Deployment 來實現 Canary (金絲雀)部署。新版本的副本和舊版本的一起發佈。在一段時間後如果沒有檢測到錯誤,則可以擴展新版本的副本數量並刪除舊版本的應用。

如果想要透過 Kubernetes 原生方法實作金絲雀部署,只能透過兩個版本副本比例來調整流量版分比,例如當 v1 與 v2 副本數都是 10 個時,流量比例即 1:1。如果你需要更精確的控制策略,建議使用 Service Mesh 服務 (如 Istio),或是透過 Ingress Controller 服務 (如 nginx)。

下面我們會透過原生 Kubernetes 實作 Canary 部署策略 。


組態檔案: my-app-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
  labels:
    app: my-app-svc
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
  - name: http
    port: 8080
    targetPort: 8080
    nodePort: 30000

組態檔案: my-app-v1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
spec:
  replicas: 10
  selector:
    matchLabels:
      app: my-app
      version: v1
  strategy:
    type: Recreate
  template:
    metadata:
      name: my-app
      labels:
        app: my-app
        version: v1
    spec:
      containers:
      - name: my-app
        image: lofairy/foo
        ports:
        - name: http
          containerPort: 8080
        livenessProbe:
          httpGet:
            path: /
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 5

組態檔案: my-app-v2.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: v2
  strategy:
    type: Recreate
  template:
    metadata:
      name: my-app
      labels:
        app: my-app
        version: v2
    spec:
      containers:
      - name: my-app
        image: lofairy/bar
        ports:
        - name: http
          containerPort: 8080
        livenessProbe:
          httpGet:
            path: /
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 5

組態檔案大致上與 Blue/Green 練習使用的一致,唯一的差別是 my-app-svc.yaml 中的 label select 去掉了標籤 version,因此 Service 會同時適用 v1 和 v2 的 Pod 應用。

接下來我們按照下面的步驟來驗證 Canary 策略:

  1. 10 個副本的 v1 應用提供服務
  2. v2 應用部署 1 個副本(意味著小於 10%的流量)
  3. 等待足夠的時間來確認 v2 應用足夠穩定沒有任何錯誤資訊
  4. 將 v2 應用擴容到 10 個副本
  5. 等待所有實例完成
  6. 關閉版本 1 應用

建立資源

  • 部署 v1 應用,以及 Service
kubectl apply -f my-app-v1.yaml my-app-svc.yaml
  • 測試 my-app 是否部署成功`
curl localhost:30000
---
{
    "message": "foo"
}

可以看到 v1 的應用正常運行了

更新部署

為了查看接下來流量切換的變化,我們進行以下動作:

  • 打開一個新終端 t1 ,運行以下命令來持續訪問 Service
while sleep 0.1; do curl http://0.0.0.0:30000; echo ""; done
  • 部署 v2 應用
kubectl apply -f my-app-v2.yaml

現在,叢集中同時有 my-app-v1, my-app-v2 兩個版本的應用

  • 回到終端 t1 ,查看結果
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"bar"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"bar"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"bar"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}

可以看到大致上有不到 10% 的流量會訪問到 v2 應用,代表金絲雀部署成功了

  • 擴大 v2 版本的副本數為 10 個
kubectl scale --replicas=10 deploy my-app-v2
  • 回到終端 t1 ,查看結果
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"foo"}
{"message":"bar"}
{"message":"bar"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"bar"}
{"message":"foo"}
{"message":"bar"}
{"message":"bar"}
{"message":"foo"}
{"message":"bar"}
{"message":"foo"}
{"message":"foo"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"foo"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"foo"}
{"message":"bar"}
{"message":"foo"}
{"message":"bar"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"bar"}
{"message":"bar"}

可以看到 v1 與 v2 版本的流量大致是 1:1。

最後,如果 v2 版本沒有問題,就可以刪除舊版的 Deployment:

kubectl delete -f my-app-v1.yaml

從上面的過程可以理解 Canary 部署有以下特性:

  • 部分使用者獲取新版本
  • 方便錯誤和性能監控
  • 快速回滾
  • 發佈較慢
  • 流量精準控制很浪費(99%A / 1%B = 99 Pod A,1 Pod B)

如果對新功能的發佈沒有信心,使用金絲雀部署是不錯的選擇。

實作: Blue/Green

在 Kubernetes 中,我們可以用兩種方法來實現 Blue/Green (藍綠)部署,通過單個 Service 對象或者 Ingress 控製器來實現藍綠部署,實際操作都是類似的,都是通過 label 標籤去控制。

下面我們會透過原生 Kubernetes Service 實作 Blue/Green 部署策略 。


組態檔案: my-app-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
  labels:
    app: my-app-svc
spec:
  type: NodePort
  selector:
    app: my-app
    version: v1
  ports:
  - name: http
    port: 8080
    targetPort: 8080
    nodePort: 30000

組態檔案: my-app-v1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v1
  template:
    metadata:
      name: my-app
      labels:
        app: my-app
        version: v1
    spec:
      containers:
      - name: my-app
        image: lofairy/foo
        ports:
        - name: http
          containerPort: 8080
        livenessProbe:
          httpGet:
            path: /
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 5

組態檔案: my-app-v2.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v2
  template:
    metadata:
      name: my-app
      labels:
        app: my-app
        version: v2
    spec:
      containers:
      - name: my-app
        image: lofairy/bar
        ports:
        - name: http
          containerPort: 8080
        livenessProbe:
          httpGet:
            path: /
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 5

組態檔案大致上與 Recreate 練習使用的一致,唯一的差別是 v1 (藍), v2 (綠) Pod 增加了新的標籤 version,用以區別不同版本的應用。因為 Blue/Green 部署的過程不改變流量切換,我們依然使用 sepc.strategy.type=Recreate 策略加快副本 scale up/down 的速度。

接下來我們按下面的步驟來驗證 Blue/Green 策略:

  1. v1 應用提供服務
  2. 部署 v2 應用
  3. 切換入口流量從 v1 到 v2
  4. 關閉 v1 應用

建立資源

  • 部署 V1 應用,以及 Service
kubectl apply -f my-app-v1.yaml my-app-svc.yaml
  • 測試 my-app 是否部署成功,訪問 localhost:30000,得到以下回應
curl localhost:30000
---
{
    "message": "foo"
}

可以看到 v1 的應用正常運行了

更新部署

  • 部署 v2 應用
kubectl apply -f my-app-v2.yaml

現在,叢集中同時有 my-app-v1, my-app-v2 兩個版本的應用,不過 Service 目前將流量都指向 my-app-v1 裡。

為了查看接下來流量切換的變化,我們進行以下動作:

  • 打開一個新終端 t1 ,運行以下命令來持續訪問 Service
while sleep 0.1; do curl http://0.0.0.0:30000; echo ""; done
  • 修改 Service 的 label selector
kubectl patch service my-app-svc -p '{"spec":{"selector":{"version":"v2"}}}'
  • 回到終端 t1 ,查看結果
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"foo"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}
{"message":"bar"}

可以看到流量在一瞬間切換成 v2 版本,並沒有中斷服務。

如果 v2 版本發生問題,可以再次修改 Service 回滾:

kubectl patch service my-app-svc -p '{"spec":{"selector":{"version":"v1"}}}'

最後,如果 v2 版本沒有問題,就可以刪除舊版的 Deployment:

kubectl delete -f my-app-v1.yaml

從上面的過程可以理解 Blue/Green 部署有以下特性:

  • 即時部署/回滾
  • 避免版本問題,因為一次更改是整個應用的改變
  • 需要兩倍的資源
  • 在發佈到生產之前,應該對整個應用進行適當的測試

在生產環境執行s無法向下相容的更新部署,Blue/Green 是很好的部署策略。

實作: A/B Testing

在原生 Kubernetes 中,並無實作 A/B testing,不過可以透過 Service Mesh 服務 (如 Istio) 或其他 Ingress Controller 服務 (如 nginx) 實現。

下面我們會透過 Nginx Ingress Controller 實作 A/B Testing 部署策略 。

開始之前

我們需要在叢集中安裝 Nginx Ingress Controller。

之前的章節中已經帶領過大家如何安裝和使用 Nginx Ingress Controller,這邊就不再贅述。


組態檔案: my-app-v1.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: my-app-v1-svc
  labels:
    app: my-app-v1-svc
spec:
  selector:
    app: my-app
    version: v1
  ports:
  - name: http
    port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v1
  strategy:
    type: Recreate
  template:
    metadata:
      name: my-app
      labels:
        app: my-app
        version: v1
    spec:
      containers:
      - name: my-app
        image: lofairy/foo
        ports:
        - name: http
          containerPort: 8080
        livenessProbe:
          httpGet:
            path: /
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 5

組態檔案: my-app-v2.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: my-app-v2-svc
  labels:
    app: my-app-v2-svc
spec:
  selector:
    app: my-app
    version: v2
  ports:
  - name: http
    port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v2
  strategy:
    type: Recreate
  template:
    metadata:
      name: my-app
      labels:
        app: my-app
        version: v2
    spec:
      containers:
      - name: my-app
        image: lofairy/bar
        ports:
        - name: http
          containerPort: 8080
        livenessProbe:
          httpGet:
            path: /
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 5

組態檔案: ingress.yaml

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30000
  selector:
    app.kubernetes.io/name: ingress-nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: main-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/canary: "false"
spec:
  ingressClassName: nginx
  rules:
  - host: "localhost"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-v1-svc
            port:
              number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: canary-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "version"
    nginx.ingress.kubernetes.io/canary-by-header-value: "v2"
spec:
  ingressClassName: nginx
  rules:
  - host: "localhost"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-v2-svc
            port:
              number: 8080

大致說明一下:

  • 應用程式組態檔案與 Canary 練習使用的一致,不過這次為兩個版本的應用都定義了一組 Service 供 Ingress 使用。
  • 我們使用 Nginx Ingress ControllerCanary 拓展實現依據 header 分流。
  • Ingress 只能對外暴露 80/443 port,為了使用 KinD 設定對本機暴露的 30000 port,我們在 Ingress 的外邊再加上一組 Service。

接下來我們按照下面的步驟來驗證 A/B Testing 策略:

  1. 部署 v1 和 v2 應用
  2. 部署 Ingress
  3. 使用不同的 header 條件,訪問到不同版本的應用

建立資源

  • 部署 v1, v2 應用,以及對應的 Service
kubectl apply -f my-app-v1.yaml -f my-app-v2.yaml
  • 部署 Ingress,以及對應的 Service
kubectl apply -f ingress.yaml
  • 驗證 my-appingress 是否部署成功
curl localhost:30000
---
{"message": "foo"}

得到回應就代表部署成功。

不過訪問 ingress 只能得到 v1 版本的回應,是因為我們在組態檔案的定義是:

  • Ingress 預設導留到 v1 應用的 Service
  • 當 Header 存在 version: v2 時,導流到 v2 應用的 Service

現在我們來試著在 Header 加入參數,再訪問 ingress:

curl -H "version: v2" http://localhost:30000
---
bar

可以看到,我們成功的以 Header 為條件將訪問者分流到不同版本的應用。

從上面的過程可以理解 A/B Testing 部署有以下特性:

  • 幾個版本平行運行
  • 完全控制流量分配
  • 特定的一個訪問錯誤難以排查,需要分佈式跟蹤
  • Kubernetes 沒有直接的支援,需要其他額外的工具

小結

Deployment strategy 三個章節中,我們已經對大部分的部署策略有了蠻深的了解。落實在實際應用中,我們需要分析產品和應用的需求,選擇合適的部署策略。沒有哪個部署策略是完美的。


參考

Kubernetes 部署策略详解-阳明的博客|Kubernetes|Istio|Prometheus|Python|Golang|云原生 (qikqiak.com)


上一篇
學 Kubernetes 的第二十三天 - Deployment strategy - 實作 (1)
下一篇
學 Kubernetes 的第二十五天 - Scheduling - 概述
系列文
都什麼年代了,還在學 Kubernetes37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言